#ifdef USE_MITM
#include <string>
#include <stdexcept>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef USE_GNUTLS
#include <gnutls/x509.h>
#include <gnutls/crypto.h>
#elif defined(USE_WOLFSSL)
#include <vector>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/rsa.h>
#include <wolfssl/wolfcrypt/ecc.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/asn.h>
#include <pthread.h>
#ifndef SSL_read
#define SSL_read wolfSSL_read
#endif
#ifndef SSL_write
#define SSL_write wolfSSL_write
#endif
#ifndef SSL_shutdown
#define SSL_shutdown wolfSSL_shutdown
#endif
#ifndef SSL_free
#define SSL_free wolfSSL_free
#endif
#ifndef SSL_CTX_free
#define SSL_CTX_free wolfSSL_CTX_free
#endif
#elif defined(USE_MBEDTLS)
#include <vector>
#include <mbedtls/build_info.h>
#include <mbedtls/platform.h>
#include <mbedtls/pk.h>
#include <mbedtls/ecdsa.h>
#include <mbedtls/rsa.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/oid.h>
#include <mbedtls/ssl.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/error.h>
#if MBEDTLS_VERSION_NUMBER < 0x03040000
#include <mbedtls/bignum.h>
#endif
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include <pthread.h>
#else
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#define CLOSESOCKET(x) closesocket(x)
#define SHUT_RDWR SD_BOTH
#define SLEEP_MS(x) Sleep(x)
#define TIMEZONE _timezone
#define TZSET _tzset
#define gmtime_r(a, b) gmtime_s(b, a)
#define timegm(a) _mkgmtime(a)
#else
#define CLOSESOCKET(x) ::close(x)
#define SLEEP_MS(x) usleep((x)*1000)
#define TIMEZONE timezone
#define TZSET tzset
#endif
#include "BBS2chProxySecureSocket.h"

extern void log_printf(int level, const char *format ...);

#if (defined(USE_WOLFSSL) && defined(WOLFSSL_ALT_NAMES)) || defined(USE_MBEDTLS)
static void setSubjectAltNames(void *ptr, std::vector<std::string> &domains, std::vector<std::string> &ipAddrs)
{
	std::vector<unsigned char> buf;
	buf.push_back(0x30);
	buf.push_back(0); // size
	for (std::vector<std::string>::iterator it = domains.begin(); it != domains.end(); ++it) {
		buf.push_back(0x82);
		buf.push_back(it->size());
		buf.insert(buf.end(), it->data(), it->data()+it->size());
	}
	for (std::vector<std::string>::iterator it = ipAddrs.begin(); it != ipAddrs.end(); ++it) {
		buf.push_back(0x87);
		buf.push_back(4);
		const char *tmp = it->c_str();
		int n[4] = {0};
		for (int i=0; i<4; i++) {
			n[i] = strtoul(tmp, (char **)&tmp, 10);
			if (n[i] > 255) n[i] = 0;
			if (tmp && *tmp == '.') {
				tmp++;
				continue;
			}
			else break;
		}
		buf.push_back(n[0]);
		buf.push_back(n[1]);
		buf.push_back(n[2]);
		buf.push_back(n[3]);
	}
	buf[1] = buf.size() - 2;
#ifdef USE_WOLFSSL
	Cert *cert = (Cert *)ptr;
	memcpy(cert->altNames, &buf.front(), buf.size());
	cert->altNamesSz = buf.size();
#else
	mbedtls_x509write_crt_set_extension((mbedtls_x509write_cert *)ptr, MBEDTLS_OID_SUBJECT_ALT_NAME, MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME), 0, &buf.front(), buf.size());
#endif
}
#endif

#ifdef USE_GNUTLS
static gnutls_x509_crt_t ca_cert;
static gnutls_x509_privkey_t ca_privkey;
static gnutls_x509_privkey_t server_privkey;
static gnutls_priority_t priority;

int BBS2chProxySecureSocket::initializeCerts(const char *certPath, const char *keyPath)
{
	static int initialized;
	if (initialized) return 0;

	gnutls_global_init();
	gnutls_datum_t data;
	int ret = gnutls_load_file(certPath, &data);
	if (ret < 0) {
		log_printf(0, "Unable to open CA certificate from %s\n", certPath);
		return -1;
	}
	gnutls_x509_crt_init(&ca_cert);
	ret = gnutls_x509_crt_import(ca_cert, &data, GNUTLS_X509_FMT_PEM);
	if (ret < 0) {
		log_printf(0, "Error loading CA certificate: %s\n", gnutls_strerror(ret));
		return -1;
	}
	gnutls_free(data.data);
	ret = gnutls_load_file(keyPath, &data);
	if (ret < 0) {
		log_printf(0, "Unable to open CA private key from %s\n", keyPath);
		return -1;
	}
	gnutls_x509_privkey_init(&ca_privkey);
	ret = gnutls_x509_privkey_import(ca_privkey, &data, GNUTLS_X509_FMT_PEM);
	if (ret < 0) {
		log_printf(0, "Error loading CA private key: %s\n", gnutls_strerror(ret));
		return -1;
	}
	gnutls_free(data.data);

	time_t expire = gnutls_x509_crt_get_expiration_time(ca_cert);
	time_t now = time(NULL);
	if (now - expire >= 0) {
		log_printf(0, "Error: CA certificate %s is expired\n", certPath);
		gnutls_x509_crt_deinit(ca_cert);
		gnutls_x509_privkey_deinit(ca_privkey);
		return -1;
	} else if (expire - now < 60*60*24*30) {
		log_printf(0, "WARNING: CA certificate %s will expire in %d days\n", certPath, 1+(int)(expire-now)/(60*60*24));
	}

	gnutls_x509_privkey_init(&server_privkey);
#ifdef USE_ECDSA_KEY
	gnutls_x509_privkey_generate(server_privkey, GNUTLS_PK_EC, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0);
#else
	gnutls_x509_privkey_generate(server_privkey, GNUTLS_PK_RSA, 2048, 0);
#endif
	gnutls_priority_init(&priority, NULL, NULL);

	initialized = 1;
	return 0;
}

void BBS2chProxySecureSocket::generateAndPrintSelfSignedCertificate(void)
{
	unsigned char tmp[4096];
	size_t bufsize = 4096;
	time_t now = time(NULL);
	unsigned long long serial;
	gnutls_x509_crt_t cert;
	gnutls_x509_privkey_t key;
	gnutls_global_init();
	gnutls_rnd(GNUTLS_RND_NONCE, &serial, sizeof(serial));
	gnutls_x509_privkey_init(&key);
#ifdef USE_ECDSA_KEY
	gnutls_x509_privkey_generate(key, GNUTLS_PK_EC, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0);
#else
	gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0);
#endif
	gnutls_x509_crt_init(&cert);
	gnutls_x509_crt_set_version(cert, 3);
	gnutls_x509_crt_set_ca_status(cert, 1);
	gnutls_x509_crt_set_activation_time(cert, now);
	gnutls_x509_crt_set_expiration_time(cert, now + 31536000*3);
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, "JP", strlen("JP"));
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "proxy2ch certificate generator", strlen("proxy2ch certificate generator"));
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, "proxy2ch", strlen("proxy2ch"));
	gnutls_x509_crt_set_serial(cert, &serial, sizeof(serial));
	gnutls_x509_crt_set_key(cert, key);
	gnutls_x509_crt_set_key_purpose_oid(cert, GNUTLS_KP_TLS_WWW_SERVER, 0);
	gnutls_x509_crt_set_key_purpose_oid(cert, GNUTLS_KP_TLS_WWW_CLIENT, 0);
	gnutls_x509_crt_set_key_usage(cert, GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_CERT_SIGN|GNUTLS_KEY_CRL_SIGN);
	gnutls_x509_crt_get_key_id(cert, 0, tmp, &bufsize);
	gnutls_x509_crt_set_subject_key_id(cert, tmp, bufsize);
	gnutls_x509_crt_sign2(cert, cert, key, GNUTLS_DIG_SHA256, 0);

	bufsize = 4096;
	gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, tmp, &bufsize);
	fwrite(tmp, 1, bufsize, stdout);
	bufsize = 4096;
	gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, tmp, &bufsize);
	fwrite(tmp, 1, bufsize, stdout);
	fflush(stdout);

	gnutls_x509_privkey_deinit(key);
	gnutls_x509_crt_deinit(cert);
}

BBS2chProxySecureSocket::BBS2chProxySecureSocket(int sock, const char *host) :
socket(sock), session(NULL), x509_cred(NULL)
{
	bool hostIsDomain = false;
	for (int i=strlen(host)-1; i>=0; i--) {
		if (host[i] != '.' && !(host[i] >= '0' && host[i] <= '9')) {
			hostIsDomain = true;
			break;
		}
	}
	unsigned char tmp[4096];
	size_t bufsize = 4096;
	time_t now = time(NULL);
	unsigned long long serial;
	gnutls_x509_crt_t cert;
	gnutls_rnd(GNUTLS_RND_NONCE, &serial, sizeof(serial));
	gnutls_x509_crt_init(&cert);
	gnutls_x509_crt_set_version(cert, 3);
	gnutls_x509_crt_set_ca_status(cert, 0);
	gnutls_x509_crt_set_activation_time(cert, now - 600);
	gnutls_x509_crt_set_expiration_time(cert, now + 31536000);
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, "JP", strlen("JP"));
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "proxy2ch", strlen("proxy2ch"));
	gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, host, strlen(host));
	gnutls_x509_crt_set_serial(cert, &serial, sizeof(serial));
	gnutls_x509_crt_set_key(cert, server_privkey);
	gnutls_x509_crt_set_key_purpose_oid(cert, GNUTLS_KP_TLS_WWW_SERVER, 0);
	gnutls_x509_crt_set_key_purpose_oid(cert, GNUTLS_KP_TLS_WWW_CLIENT, 0);
	gnutls_x509_crt_set_key_usage(cert, GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT);
	gnutls_x509_crt_get_key_id(cert, 0, tmp, &bufsize);
	gnutls_x509_crt_set_subject_key_id(cert, tmp, bufsize);
	gnutls_x509_crt_set_subject_alt_name(cert, hostIsDomain ? GNUTLS_SAN_DNSNAME : GNUTLS_SAN_IPADDRESS, host, strlen(host), GNUTLS_FSAN_APPEND);
	gnutls_x509_crt_set_subject_alt_name(cert, GNUTLS_SAN_DNSNAME, "*.5ch.net", strlen("*.5ch.net"), GNUTLS_FSAN_APPEND);
	gnutls_x509_crt_set_subject_alt_name(cert, GNUTLS_SAN_DNSNAME, "*.2ch.net", strlen("*.2ch.net"), GNUTLS_FSAN_APPEND);
	gnutls_x509_crt_set_subject_alt_name(cert, GNUTLS_SAN_DNSNAME, "*.bbspink.com", strlen("*.bbspink.com"), GNUTLS_FSAN_APPEND);
	gnutls_x509_crt_sign2(cert, ca_cert, ca_privkey, GNUTLS_DIG_SHA256, 0);

	gnutls_certificate_allocate_credentials(&x509_cred);
	gnutls_certificate_set_x509_key(x509_cred, &cert, 1, server_privkey);
	gnutls_x509_crt_deinit(cert);
#if GNUTLS_VERSION_NUMBER >= 0x030506
	gnutls_certificate_set_known_dh_params(x509_cred, GNUTLS_SEC_PARAM_MEDIUM);
#endif

	int ret = gnutls_init(&session, GNUTLS_SERVER);
	if (ret < 0) {
		gnutls_certificate_free_credentials(x509_cred);
		std::string str("Unable to create GnuTLS session: ");
		str += gnutls_strerror(ret);
		throw std::runtime_error(str);
	}
	gnutls_priority_set(session, priority);
	ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
	if (ret < 0) {
		gnutls_deinit(session);
		gnutls_certificate_free_credentials(x509_cred);
		std::string str("Unable to set server credentials: ");
		str += gnutls_strerror(ret);
		throw std::runtime_error(str);
	}
	gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE);
	gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
	gnutls_transport_set_int(session, sock);
	ret = gnutls_handshake(session);
	if (ret < 0) {
		gnutls_deinit(session);
		gnutls_certificate_free_credentials(x509_cred);
		std::string str("Unable to establish SSL/TLS connection: ");
		str += gnutls_strerror(ret);
		throw std::runtime_error(str);
	}
#if GNUTLS_VERSION_NUMBER >= 0x030704
	log_printf(1, "MITM connection established with %s, %s\n", gnutls_ciphersuite_get(session), gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
#else
	log_printf(1, "MITM connection established with %s, %s\n", gnutls_cipher_suite_get_name(gnutls_kx_get(session), gnutls_cipher_get(session), gnutls_mac_get(session)), gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
#endif
}

#elif defined(USE_WOLFSSL)
#if !defined(WOLFSSL_CERT_GEN) || !defined(WOLFSSL_CERT_EXT) || !defined(WOLFSSL_ALT_NAMES)
#error Some optional features are missing in your wolfSSL configuration. Configure and build with --enable-certgen, --enable-certext, ENABLED_ALTNAMES=yes options.
#endif
#if !(defined(USE_ECDSA_KEY) && defined(HAVE_ECC)) && !defined(WOLFSSL_KEY_GEN)
#error wolfSSL should be configured and built with --enable-keygen option to use with RSA key.
#endif
#if defined(WOLFSSL_TEST_CERT) && LIBWOLFSSL_VERSION_HEX < 0x05001000
#define wc_InitDecodedCert InitDecodedCert
#define wc_ParseCert ParseCert
#define wc_FreeDecodedCert FreeDecodedCert
#endif

static std::vector<unsigned char> ca_cert;
static RsaKey ca_rsa_key;
static ecc_key ca_ecc_key;
static RsaKey *ca_rsa_key_ptr;
static ecc_key *ca_ecc_key_ptr;
static RsaKey server_rsa_key;
static ecc_key server_ecc_key;
static RsaKey *server_rsa_key_ptr;
static ecc_key *server_ecc_key_ptr;
static std::vector<unsigned char> server_privkey;
static pthread_mutex_t mutex;

int BBS2chProxySecureSocket::initializeCerts(const char *certPath, const char *keyPath)
{
	static int initialized;
	if (initialized) return 0;

	wolfSSL_Init();

	std::vector<unsigned char> buf;
	std::vector<unsigned char> ca_key;
	FILE *fp = fopen(certPath, "rb");
	if (!fp) {
		log_printf(0, "Unable to open CA certificate from %s\n", certPath);
		return -1;
	}
	fseek(fp, 0, SEEK_END);
	size_t size = ftell(fp);
	rewind(fp);
	buf.resize(size);
	ca_cert.resize(size);
	fread(&buf.front(), 1, size, fp);
	fclose(fp);
	int certSize = wolfSSL_CertPemToDer(&buf.front(), buf.size(), &ca_cert.front(), ca_cert.size(), CERT_TYPE);
	if (certSize > 0) {
		ca_cert.resize(certSize);
#ifndef NO_ASN_TIME
#if defined(OPENSSL_EXTRA) && LIBWOLFSSL_VERSION_HEX >= 0x04002000 // wolfSSL 4.2.0 or later with OPENSSL_EXTRA
		WOLFSSL_X509 *cert = wolfSSL_X509_d2i(NULL, &ca_cert.front(), ca_cert.size());
		if (!cert) {
			log_printf(0, "Error parsing CA certificate\n");
			return -1;
		}
		WOLFSSL_ASN1_TIME *expire = wolfSSL_X509_get_notAfter(cert);
		if (wolfSSL_X509_cmp_current_time(expire) < 0) {
			log_printf(0, "Error: CA certificate %s is expired\n", certPath);
			wolfSSL_X509_free(cert);
			return -1;
		} else {
			int day, sec;
			wolfSSL_ASN1_TIME_diff(&day, &sec, NULL, expire);
			if (day < 30)
				log_printf(0, "WARNING: CA certificate %s will expire in %d days\n", certPath, day+(sec?1:0));
		}
		wolfSSL_X509_free(cert);
#elif LIBWOLFSSL_VERSION_HEX >= 0x05001000 || ((defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)) && LIBWOLFSSL_VERSION_HEX >= 0x03015000)
		byte asnDateFormat;
		const byte *asnDate;
		int asnDateLength = 0;
#if LIBWOLFSSL_VERSION_HEX >= 0x05001000 || defined(WOLFSSL_TEST_CERT)
#define CERT_FREE wc_FreeDecodedCert
		/* DER decoder APIs are not exposed unless WOLFSSL_TEST_CERT is set until 5.1.0 */
		DecodedCert cert_;
		DecodedCert *cert = &cert_;
		wc_InitDecodedCert(cert, &ca_cert.front(), ca_cert.size(), NULL);
		if (wc_ParseCert(cert, CERT_TYPE, NO_VERIFY, NULL) != 0) {
			log_printf(0, "Error parsing CA certificate\n");
			wc_FreeDecodedCert(cert);
			return -1;
		}
		if (wc_GetDateInfo(cert_.afterDate, cert_.afterDateLen, &asnDate, &asnDateFormat, &asnDateLength) != 0) {
			asnDateLength = 0;
		}
#else
#define CERT_FREE wolfSSL_X509_free
		/* wolfSSL_X509_d2i is always available but wolfSSL_X509_free is not available unless OPENSSL_EXTRA or OPENSSL_EXTRA_X509_SMALL is set */
		WOLFSSL_X509 *cert = wolfSSL_X509_d2i(NULL, &ca_cert.front(), ca_cert.size());
		if (!cert) {
			log_printf(0, "Error parsing CA certificate\n");
			return -1;
		}
		const byte *ptr = wolfSSL_X509_notAfter(cert);
		if (ptr) {
			asnDateFormat = ptr[0];
			asnDateLength = ptr[1];
			asnDate = ptr+2;
		}
#endif
		if (asnDateLength) {
			struct tm notAfter = {0};
			if (wc_GetDateAsCalendarTime(asnDate, asnDateLength, asnDateFormat, &notAfter) == 0) {
#ifdef NO_TIMEGM
				TZSET();
				time_t expire = mktime(&notAfter) - TIMEZONE;
#else
				time_t expire = timegm(&notAfter);
#endif
				time_t now = time(NULL);
				if (now - expire >= 0) {
					log_printf(0, "Error: CA certificate %s is expired\n", certPath);
					CERT_FREE(cert);
					return -1;
				} else if (expire - now < 60*60*24*30) {
					log_printf(0, "WARNING: CA certificate %s will expire in %d days\n", certPath, 1+(int)(expire-now)/(60*60*24));
				}
			}
		}
		CERT_FREE(cert);
#endif
#endif
	} else {
		char errbuf[256];
		wolfSSL_ERR_error_string_n(certSize, errbuf, 256);
		log_printf(0, "Error loading CA certificate: %s\n", errbuf);
		return -1;
	}

	fp = fopen(keyPath, "rb");
	if (!fp) {
		log_printf(0, "Unable to open CA private key from %s\n", keyPath);
		return -1;
	}
	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	rewind(fp);
	buf.resize(size);
	ca_key.resize(size);
	fread(&buf.front(), 1, size, fp);
	fclose(fp);
	int keySize = wolfSSL_KeyPemToDer(&buf.front(), buf.size(), &ca_key.front(), ca_key.size(), NULL);
	if (keySize > 0) {
		ca_key.resize(keySize);
	} else {
		char errbuf[256];
		wolfSSL_ERR_error_string_n(keySize, errbuf, 256);
		log_printf(0, "Error loading CA private key: %s\n", errbuf);
		return -1;
	}
#if defined(HAVE_ECC)
	{
		word32 idx = 0;
		wc_ecc_init(&ca_ecc_key);
		int ret = wc_EccPrivateKeyDecode(&ca_key.front(), &idx, &ca_ecc_key, ca_key.size());
		if (ret == 0) {
			ca_ecc_key_ptr = &ca_ecc_key;
		} else {
			wc_ecc_free(&ca_ecc_key);
		}
	}
#endif
	if (!ca_ecc_key_ptr) {
		word32 idx = 0;
		wc_InitRsaKey(&ca_rsa_key, NULL);
		int ret = wc_RsaPrivateKeyDecode(&ca_key.front(), &idx, &ca_rsa_key, ca_key.size());
		if (ret == 0) {
			ca_rsa_key_ptr = &ca_rsa_key;
		} else {
			wc_FreeRsaKey(&ca_rsa_key);
		}
	}
	if (!ca_ecc_key_ptr && !ca_rsa_key_ptr) {
#if defined(HAVE_ECC)
		log_printf(0, "Error: CA key file should include either RSA or EC private key\n");
#else
		log_printf(0, "Error: CA key file should include RSA private key\n");
#endif
		return -1;
	}
	WC_RNG rng;
	wc_InitRng(&rng);
	server_privkey.resize(4096);
#if defined(HAVE_ECC) && defined(USE_ECDSA_KEY)
	wc_ecc_init(&server_ecc_key);
	wc_ecc_make_key_ex(&rng, 32, &server_ecc_key, ECC_SECP256R1);
	keySize = wc_EccKeyToDer(&server_ecc_key, &server_privkey.front(), server_privkey.size());
	server_privkey.resize(keySize);
	server_ecc_key_ptr = &server_ecc_key;
#else
	wc_InitRsaKey(&server_rsa_key, NULL);
	wc_MakeRsaKey(&server_rsa_key, 2048, WC_RSA_EXPONENT, &rng);
	keySize = wc_RsaKeyToDer(&server_rsa_key, &server_privkey.front(), server_privkey.size());
	server_privkey.resize(keySize);
	server_rsa_key_ptr = &server_rsa_key;
#endif
	wc_FreeRng(&rng);
	pthread_mutex_init(&mutex, NULL);
	initialized = 1;
	return 0;
}

void BBS2chProxySecureSocket::generateAndPrintSelfSignedCertificate(void)
{
	Cert cert;
	WC_RNG rng;
	RsaKey *rsaKeyPtr = NULL;
	ecc_key *eccKeyPtr = NULL;
	std::vector<unsigned char>buf, keyPemBuf, certPemBuf;
	buf.resize(4096);
	keyPemBuf.resize(4096);
	certPemBuf.resize(4096);
	wc_InitRng(&rng);
#if defined(HAVE_ECC) && defined(USE_ECDSA_KEY)
	ecc_key key;
	wc_ecc_init(&key);
	wc_ecc_make_key_ex(&rng, 32, &key, ECC_SECP256R1);
	eccKeyPtr = &key;
	int keyType = ECC_PRIVATEKEY_TYPE;
	int keySize = wc_EccKeyToDer(&key, &buf.front(), buf.size());
	int sigType = CTC_SHA256wECDSA;
#else
	RsaKey key;
	wc_InitRsaKey(&key, NULL);
	wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng);
	rsaKeyPtr = &key;
	int keyType = PRIVATEKEY_TYPE;
	int keySize = wc_RsaKeyToDer(&key, &buf.front(), buf.size());
	int sigType = CTC_SHA256wRSA;
#endif
	int keyPemSize = wc_DerToPem(&buf.front(), keySize, &keyPemBuf.front(), keyPemBuf.size(), keyType);
	keyPemBuf.resize(keyPemSize);

	wc_InitCert(&cert);
	strncpy(cert.subject.country, "JP", CTC_NAME_SIZE);
	strncpy(cert.subject.org, "proxy2ch certificate generator", CTC_NAME_SIZE);
	strncpy(cert.subject.commonName, "proxy2ch", CTC_NAME_SIZE);
	cert.isCA = 1;
	cert.sigType = sigType;
	cert.daysValid = 365*3;
	wc_SetKeyUsage(&cert, "digitalSignature,keyCertSign,cRLSign");
	wc_SetExtKeyUsage(&cert, "serverAuth,clientAuth");
	wc_SetSubjectKeyIdFromPublicKey(&cert, rsaKeyPtr, eccKeyPtr);
	wc_MakeCert(&cert, &buf.front(), buf.size(), rsaKeyPtr, eccKeyPtr, &rng);
	int certSize = wc_SignCert(cert.bodySz, cert.sigType, &buf.front(), buf.size(), rsaKeyPtr, eccKeyPtr, &rng);
	int certPemSize = wc_DerToPem(&buf.front(), certSize, &certPemBuf.front(), certPemBuf.size(), CERT_TYPE);
	certPemBuf.resize(certPemSize);

	if (rsaKeyPtr) wc_FreeRsaKey(rsaKeyPtr);
	if (eccKeyPtr) wc_ecc_free(eccKeyPtr);
	wc_FreeRng(&rng);
	fwrite(&certPemBuf.front(), 1, certPemBuf.size(), stdout);
	fwrite(&keyPemBuf.front(), 1, keyPemBuf.size(), stdout);
	fflush(stdout);
}

BBS2chProxySecureSocket::BBS2chProxySecureSocket(int sock, const char *host) :
socket(sock), ctx(NULL), ssl(NULL)
{
	bool hostIsDomain = false;
	for (int i=strlen(host)-1; i>=0; i--) {
		if (host[i] != '.' && !(host[i] >= '0' && host[i] <= '9')) {
			hostIsDomain = true;
			break;
		}
	}

	Cert cert;
	WC_RNG rng;
	std::vector<unsigned char>certBuf;
	certBuf.resize(4096);
	wc_InitRng(&rng);
	wc_InitCert(&cert);
	strncpy(cert.subject.country, "JP", CTC_NAME_SIZE);
	strncpy(cert.subject.org, "proxy2ch", CTC_NAME_SIZE);
	strncpy(cert.subject.commonName, host, CTC_NAME_SIZE);
	cert.isCA = 0;
	cert.sigType = ca_ecc_key_ptr ? CTC_SHA256wECDSA : CTC_SHA256wRSA;
	cert.daysValid = 365;
	wc_SetKeyUsage(&cert, "digitalSignature,keyEncipherment");
	wc_SetExtKeyUsage(&cert, "serverAuth,clientAuth");
	std::vector<std::string> domains, ipAddrs;
	domains.push_back("*.5ch.net");
	domains.push_back("*.2ch.net");
	domains.push_back("*.bbspink.com");
	if (hostIsDomain) domains.push_back(host);
	else ipAddrs.push_back(host);
	setSubjectAltNames(&cert, domains, ipAddrs);
	wc_SetIssuerBuffer(&cert, &ca_cert.front(), ca_cert.size());
	pthread_mutex_lock(&mutex);
	wc_SetSubjectKeyIdFromPublicKey(&cert, server_rsa_key_ptr, server_ecc_key_ptr);
	if (wc_MakeCert(&cert, &certBuf.front(), certBuf.size(), server_rsa_key_ptr, server_ecc_key_ptr, &rng) <= 0) {
		wc_FreeRng(&rng);
		pthread_mutex_unlock(&mutex);
		throw std::runtime_error("Unable to make server certificate");
	}
	int certSize = wc_SignCert(cert.bodySz, cert.sigType, &certBuf.front(), certBuf.size(), ca_rsa_key_ptr, ca_ecc_key_ptr, &rng);
	if (certSize <= 0) {
		wc_FreeRng(&rng);
		pthread_mutex_unlock(&mutex);
		throw std::runtime_error("Unable to sign server certificate");
	}
	certBuf.resize(certSize);
	wc_FreeRng(&rng);
	pthread_mutex_unlock(&mutex);

#if LIBWOLFSSL_VERSION_HEX < 0x04002000
	ctx = wolfSSL_CTX_new(wolfSSLv23_server_method());
#else
	ctx = wolfSSL_CTX_new(wolfTLS_server_method());
#endif
	if (!ctx) {
		throw std::runtime_error("Unable to create SSL context");
	}
#if defined(WOLFSSL_ALLOW_SSLV3)
	wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_SSLV3);
#endif
	if (wolfSSL_CTX_use_certificate_buffer(ctx, &certBuf.front(), certBuf.size(), WOLFSSL_FILETYPE_ASN1) <= 0) {
		log_printf(0, "Unable to load server certificate\n");
	}
	if (wolfSSL_CTX_use_PrivateKey_buffer(ctx, &server_privkey.front(), server_privkey.size(), WOLFSSL_FILETYPE_ASN1) <= 0) {
		log_printf(0, "Unable to load server private key\n");
	}
	ssl = wolfSSL_new(ctx);
	wolfSSL_set_fd(ssl, socket);
	int ret = wolfSSL_accept(ssl);
	if (ret != SSL_SUCCESS) {
		char errbuf[256];
		wolfSSL_ERR_error_string_n(wolfSSL_get_error(ssl, ret), errbuf, 256);
		wolfSSL_free(ssl);
		wolfSSL_CTX_free(ctx);
		std::string str("Unable to establish SSL/TLS connection: ");
		str += errbuf;
		throw std::runtime_error(str);
	}
	log_printf(1, "MITM connection established with %s, %s\n", wolfSSL_get_cipher_name(ssl), wolfSSL_get_version(ssl));
}

#elif defined(USE_MBEDTLS)
static std::vector<char> ca_subject;
static mbedtls_pk_context ca_privkey;
static mbedtls_pk_context server_privkey;
static mbedtls_entropy_context entropy;
static mbedtls_ctr_drbg_context ctr_drbg;
static pthread_mutex_t mutex;

static void setDaysValid(mbedtls_x509write_cert *cert, int days)
{
	time_t now = time(NULL);
	time_t expire = now + days*24*60*60;
	now -= 600;
	struct tm notBeFore = {0}, notAfter = {0};
	gmtime_r(&now, &notBeFore);
	gmtime_r(&expire, &notAfter);
	char from[32], to[32];
	strftime(from, 32, "%Y%m%d%H%M%S", &notBeFore);
	strftime(to, 32, "%Y%m%d%H%M%S", &notAfter);
	mbedtls_x509write_crt_set_validity(cert, from, to);
}

static void setExtKeyUsage(mbedtls_x509write_cert *cert, mbedtls_asn1_sequence *sequence)
{
	int size;
	unsigned char *start, *end;
	std::vector<unsigned char> buf;
	mbedtls_asn1_sequence extKeyUsage[2];
	if (!sequence) {
		extKeyUsage[0].buf.tag = MBEDTLS_ASN1_OID;
		extKeyUsage[0].buf.len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
		extKeyUsage[0].buf.p = (unsigned char *)MBEDTLS_OID_SERVER_AUTH;
		extKeyUsage[0].next = &extKeyUsage[1];
		extKeyUsage[1].buf.tag = MBEDTLS_ASN1_OID;
		extKeyUsage[1].buf.len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
		extKeyUsage[1].buf.p = (unsigned char *)MBEDTLS_OID_CLIENT_AUTH;
		extKeyUsage[1].next = NULL;
		sequence = &extKeyUsage[0];
	}
#if 1
	buf.push_back(0x30);
	buf.push_back(0); // size
	for (mbedtls_asn1_sequence *ptr = sequence; ptr; ptr = ptr->next) {
		buf.push_back(ptr->buf.tag);
		buf.push_back(ptr->buf.len);
		buf.insert(buf.end(), ptr->buf.p, ptr->buf.p+ptr->buf.len);
	}
	buf[1] = buf.size() - 2;
	mbedtls_x509write_crt_set_extension(cert, MBEDTLS_OID_EXTENDED_KEY_USAGE, MBEDTLS_OID_SIZE(MBEDTLS_OID_EXTENDED_KEY_USAGE), 0, &buf.front(), buf.size());
#else // available on 3.2.0 or later
	mbedtls_x509write_crt_set_ext_key_usage(cert, sequence);
#endif
}

int BBS2chProxySecureSocket::initializeCerts(const char *certPath, const char *keyPath)
{
	static int initialized;
	if (initialized) return 0;

	mbedtls_x509_crt ca_cert;
	int ret;
	mbedtls_entropy_init(&entropy);
	mbedtls_ctr_drbg_init(&ctr_drbg);
	mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)"proxy2ch", 8);
	mbedtls_x509_crt_init(&ca_cert);
	if ((ret = mbedtls_x509_crt_parse_file(&ca_cert, certPath)) != 0) {
		char errbuf[256];
		mbedtls_strerror(ret, errbuf, 256);
		log_printf(0, "Error loading CA certificate: %s\n", errbuf);
		mbedtls_x509_crt_free(&ca_cert);
		mbedtls_ctr_drbg_free(&ctr_drbg);
		mbedtls_entropy_free(&entropy);
		return -1;
	}
	else {
		struct tm notAfter = {0};
		notAfter.tm_year = ca_cert.valid_to.year - 1900;
		notAfter.tm_mon = ca_cert.valid_to.mon - 1;
		notAfter.tm_mday = ca_cert.valid_to.day;
		notAfter.tm_hour = ca_cert.valid_to.hour;
		notAfter.tm_min = ca_cert.valid_to.min;
		notAfter.tm_sec = ca_cert.valid_to.sec;
#ifdef NO_TIMEGM
		TZSET();
		time_t expire = mktime(&notAfter) - TIMEZONE;
#else
		time_t expire = timegm(&notAfter);
#endif
		time_t now = time(NULL);
		if (now - expire >= 0) {
			log_printf(0, "Error: CA certificate %s is expired\n", certPath);
			mbedtls_x509_crt_free(&ca_cert);
			return -1;
		} else if (expire - now < 60*60*24*30) {
			log_printf(0, "WARNING: CA certificate %s will expire in %d days\n", certPath, 1+(int)(expire-now)/(60*60*24));
		}
	}
	char buf[2048];
	ret = mbedtls_x509_dn_gets(buf, 2048, &ca_cert.subject);
	if (ret <= 0) {
		log_printf(0, "Error reading CA subject name from CA certificate\n");
		mbedtls_x509_crt_free(&ca_cert);
		return -1;
	}
	ca_subject.resize(ret+1);
	memcpy(&ca_subject.front(), buf, ret);
	mbedtls_x509_crt_free(&ca_cert);
	mbedtls_pk_init(&ca_privkey);
#if MBEDTLS_VERSION_NUMBER >= 0x03000000 && MBEDTLS_VERSION_NUMBER < 0x04000000
	if ((ret = mbedtls_pk_parse_keyfile(&ca_privkey, keyPath, NULL, mbedtls_ctr_drbg_random, &ctr_drbg)) != 0)
#else
		if ((ret = mbedtls_pk_parse_keyfile(&ca_privkey, keyPath, NULL)) != 0)
#endif
		{
			char errbuf[256];
			mbedtls_strerror(ret, errbuf, 256);
			log_printf(0, "Error loading CA private key: %s\n", errbuf);
			mbedtls_pk_free(&ca_privkey);
			mbedtls_ctr_drbg_free(&ctr_drbg);
			mbedtls_entropy_free(&entropy);
			return -1;
		}
#if defined(MBEDTLS_ECP_C) && defined(USE_ECDSA_KEY)
		mbedtls_pk_setup(&server_privkey, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
		mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(server_privkey), mbedtls_ctr_drbg_random, &ctr_drbg);
#else
		mbedtls_pk_setup(&server_privkey, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
		mbedtls_rsa_gen_key(mbedtls_pk_rsa(server_privkey), mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 65537);
#endif
		pthread_mutex_init(&mutex, NULL);
		initialized = 1;
		return 0;
	}

void BBS2chProxySecureSocket::generateAndPrintSelfSignedCertificate(void)
{
	mbedtls_entropy_context entropy_;
	mbedtls_ctr_drbg_context ctr_drbg_;
	mbedtls_pk_context key;
	mbedtls_x509write_cert cert;
	std::vector<unsigned char> buf;
	buf.resize(4096);
	mbedtls_entropy_init(&entropy_);
	mbedtls_pk_init(&key);
	mbedtls_ctr_drbg_init(&ctr_drbg_);
	mbedtls_ctr_drbg_seed(&ctr_drbg_, mbedtls_entropy_func, &entropy_, (const unsigned char *)"proxy2ch", 8);
#if defined(MBEDTLS_ECP_C) && defined(USE_ECDSA_KEY)
	mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
	mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(key), mbedtls_ctr_drbg_random, &ctr_drbg_);
#else
	mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
	mbedtls_rsa_gen_key(mbedtls_pk_rsa(key), mbedtls_ctr_drbg_random, &ctr_drbg_, 2048, 65537);
#endif
	mbedtls_x509write_crt_init(&cert);
	mbedtls_x509write_crt_set_subject_key(&cert, &key);
	mbedtls_x509write_crt_set_issuer_key(&cert, &key);
	mbedtls_x509write_crt_set_subject_name(&cert, "C=JP,O=proxy2ch certificate generator,CN=proxy2ch");
	mbedtls_x509write_crt_set_issuer_name(&cert, "C=JP,O=proxy2ch certificate generator,CN=proxy2ch");
	mbedtls_x509write_crt_set_version(&cert, MBEDTLS_X509_CRT_VERSION_3);
	mbedtls_x509write_crt_set_md_alg(&cert, MBEDTLS_MD_SHA256);
	mbedtls_ctr_drbg_random(&ctr_drbg_, &buf.front(), 16);
#if MBEDTLS_VERSION_NUMBER >= 0x03040000
	mbedtls_x509write_crt_set_serial_raw(&cert, &buf.front(), 16);
#else
	mbedtls_mpi serial;
	mbedtls_mpi_init(&serial);
	mbedtls_mpi_read_binary(&serial, &buf.front(), 16);
	mbedtls_x509write_crt_set_serial(&cert, &serial);
	mbedtls_mpi_free(&serial);
#endif
	setDaysValid(&cert, 365*3);
	mbedtls_x509write_crt_set_basic_constraints(&cert, 1, -1);
	mbedtls_x509write_crt_set_subject_key_identifier(&cert);
	mbedtls_x509write_crt_set_key_usage(&cert, MBEDTLS_X509_KU_DIGITAL_SIGNATURE|MBEDTLS_X509_KU_KEY_CERT_SIGN|MBEDTLS_X509_KU_CRL_SIGN);
	setExtKeyUsage(&cert, NULL);
#if MBEDTLS_VERSION_NUMBER >= 0x04000000
	mbedtls_x509write_crt_pem(&cert, &buf.front(), buf.size());
#else
	mbedtls_x509write_crt_pem(&cert, &buf.front(), buf.size(), mbedtls_ctr_drbg_random, &ctr_drbg_);
#endif
	fwrite(&buf.front(), 1, strlen((char *)&buf.front()), stdout);
	mbedtls_pk_write_key_pem(&key, &buf.front(), buf.size());
	fwrite(&buf.front(), 1, strlen((char *)&buf.front()), stdout);
	fflush(stdout);
	mbedtls_x509write_crt_free(&cert);
	mbedtls_pk_free(&key);
	mbedtls_ctr_drbg_free(&ctr_drbg_);
	mbedtls_entropy_free(&entropy_);
}

BBS2chProxySecureSocket::BBS2chProxySecureSocket(int sock, const char *host) : socket(sock)
{
	client.fd = -1;
	bool hostIsDomain = false;
	for (int i=strlen(host)-1; i>=0; i--) {
		if (host[i] != '.' && !(host[i] >= '0' && host[i] <= '9')) {
			hostIsDomain = true;
			break;
		}
	}
	int ret;
	std::vector<unsigned char> buf;
	buf.resize(4096);
	mbedtls_x509write_cert cert;
	mbedtls_x509write_crt_init(&cert);
	mbedtls_x509write_crt_set_subject_key(&cert, &server_privkey);
	mbedtls_x509write_crt_set_issuer_key(&cert, &ca_privkey);
	std::string subjectName = "C=JP,O=proxy2ch,CN=";
	subjectName += host;
	mbedtls_x509write_crt_set_subject_name(&cert, subjectName.c_str());
	mbedtls_x509write_crt_set_issuer_name(&cert, &ca_subject.front());
	mbedtls_x509write_crt_set_version(&cert, MBEDTLS_X509_CRT_VERSION_3);
	mbedtls_x509write_crt_set_md_alg(&cert, MBEDTLS_MD_SHA256);
	mbedtls_ctr_drbg_random(&ctr_drbg, &buf.front(), 16);
#if MBEDTLS_VERSION_NUMBER >= 0x03040000
	mbedtls_x509write_crt_set_serial_raw(&cert, &buf.front(), 16);
#else
	mbedtls_mpi serial;
	mbedtls_mpi_init(&serial);
	mbedtls_mpi_read_binary(&serial, &buf.front(), 16);
	mbedtls_x509write_crt_set_serial(&cert, &serial);
	mbedtls_mpi_free(&serial);
#endif
	setDaysValid(&cert, 365);
	mbedtls_x509write_crt_set_basic_constraints(&cert, 0, -1);
	mbedtls_x509write_crt_set_subject_key_identifier(&cert);
	mbedtls_x509write_crt_set_key_usage(&cert, MBEDTLS_X509_KU_DIGITAL_SIGNATURE|MBEDTLS_X509_KU_KEY_ENCIPHERMENT);
	setExtKeyUsage(&cert, NULL);
	std::vector<std::string> domains, ipAddrs;
	domains.push_back("*.5ch.net");
	domains.push_back("*.2ch.net");
	domains.push_back("*.bbspink.com");
	if (hostIsDomain) domains.push_back(host);
	else ipAddrs.push_back(host);
	setSubjectAltNames(&cert, domains, ipAddrs);
	pthread_mutex_lock(&mutex);
#if MBEDTLS_VERSION_NUMBER >= 0x04000000
	ret = mbedtls_x509write_crt_der(&cert, &buf.front(), buf.size());
#else
	ret = mbedtls_x509write_crt_der(&cert, &buf.front(), buf.size(), mbedtls_ctr_drbg_random, &ctr_drbg);
#endif
	pthread_mutex_unlock(&mutex);
	if (ret <= 0) {
		char errbuf[256];
		mbedtls_strerror(ret, errbuf, 256);
		std::string str = "Unable to make server certificate: ";
		str += errbuf;
		throw std::runtime_error(str);
	}
	mbedtls_x509write_crt_free(&cert);
	mbedtls_x509_crt_init(&server_cert);
	ret = mbedtls_x509_crt_parse_der(&server_cert, &buf.front()+buf.size()-ret, ret);
	if (ret < 0) {
		char errbuf[256];
		mbedtls_strerror(ret, errbuf, 256);
		std::string str = "Unable to load server certificate: ";
		str += errbuf;
		throw std::runtime_error(str);
	}
	mbedtls_ssl_config_init(&conf);
	mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
	mbedtls_ssl_conf_ca_chain(&conf, server_cert.next, NULL);
	mbedtls_ssl_conf_own_cert(&conf, &server_cert, &server_privkey);
#if MBEDTLS_VERSION_NUMBER < 0x04000000
	mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
#endif
	mbedtls_ssl_init(&ssl);
	ret = mbedtls_ssl_setup(&ssl, &conf);
	if (ret < 0) {
		char errbuf[256];
		mbedtls_strerror(ret, errbuf, 256);
		mbedtls_ssl_free(&ssl);
		mbedtls_ssl_config_free(&conf);
		mbedtls_x509_crt_free(&server_cert);
		std::string str = "Unable to create SSL context: ";
		str += errbuf;
		throw std::runtime_error(str);
	}
	client.fd = sock;
	mbedtls_ssl_set_bio(&ssl, &client, mbedtls_net_send, mbedtls_net_recv, NULL);
	pthread_mutex_lock(&mutex);
	ret = mbedtls_ssl_handshake(&ssl);
	if (ret < 0) {
		char errbuf[256];
		mbedtls_strerror(ret, errbuf, 256);
		mbedtls_ssl_free(&ssl);
		mbedtls_ssl_config_free(&conf);
		mbedtls_x509_crt_free(&server_cert);
		pthread_mutex_unlock(&mutex);
		client.fd = -1;
		std::string str = "Unable to establish SSL/TLS connection: ";
		str += errbuf;
		throw std::runtime_error(str);
	}
	pthread_mutex_unlock(&mutex);
	log_printf(1, "MITM connection established with %s, %s\n", mbedtls_ssl_get_ciphersuite(&ssl), mbedtls_ssl_get_version(&ssl));
}

#else
static X509 *ca_cert;
static EVP_PKEY *ca_privkey;
static EVP_PKEY *server_privkey;

static int add_ext(X509 *cert, int nid, const char *value)
{
	X509_EXTENSION *ex;
	X509V3_CTX ctx;
	X509V3_set_ctx_nodb(&ctx);
	X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
	ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
	if (!ex)
		return 0;

	X509_add_ext(cert,ex,-1);
	X509_EXTENSION_free(ex);
	return 1;
}

int BBS2chProxySecureSocket::initializeCerts(const char *certPath, const char *keyPath)
{
	static int initialized;
	if (initialized) return 0;

	FILE *fp = fopen(certPath, "rb");
	if (!fp) {
		log_printf(0, "Unable to open CA certificate from %s\n", certPath);
		return -1;
	}
	ca_cert = PEM_read_X509(fp, NULL, NULL, NULL);
	if (!ca_cert) {
		log_printf(0, "Error loading CA certificate: ");
		ERR_print_errors_fp(stderr);
		return -1;
	}
	fclose(fp);
	fp = fopen(keyPath, "rb");
	if (!fp) {
		log_printf(0, "Unable to open CA private key from %s\n", keyPath);
		return -1;
	}
	ca_privkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
	if (!ca_privkey) {
		log_printf(0, "Error loading CA private key: ");
		ERR_print_errors_fp(stderr);
		return -1;
	}
	fclose(fp);

	const ASN1_TIME *expire = X509_get0_notAfter(ca_cert);
	if (X509_cmp_current_time(expire) < 0) {
		log_printf(0, "Error: CA certificate %s is expired\n", certPath);
		X509_free(ca_cert);
		EVP_PKEY_free(ca_privkey);
		return -1;
	}
#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x3050000fL
	else {
		int day, sec;
		ASN1_TIME_diff(&day, &sec, NULL, expire);
		if (day < 30)
			log_printf(0, "WARNING: CA certificate %s will expire in %d days\n", certPath, day+(sec?1:0));
	}
#endif

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#if defined(USE_ECDSA_KEY)
	server_privkey = EVP_EC_gen("P-256");
#else
	server_privkey = EVP_RSA_gen(2048);
#endif
#else
	server_privkey = EVP_PKEY_new();
#if defined(USE_ECDSA_KEY) && (OPENSSL_VERSION_NUMBER >= 0x10000000L)
	EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
	EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE);
	EC_KEY_generate_key(ec_key);
	EVP_PKEY_assign_EC_KEY(server_privkey, ec_key);
#else
	BIGNUM *bn = BN_new();
	BN_set_word(bn, RSA_F4);
	RSA *rsa = RSA_new();
	RSA_generate_key_ex(rsa, 2048, bn, NULL);
	EVP_PKEY_assign_RSA(server_privkey, rsa);
	BN_free(bn);
#endif
#endif

	initialized = 1;
	return 0;
}

void BBS2chProxySecureSocket::generateAndPrintSelfSignedCertificate(void)
{
	BIGNUM *bn;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#if defined(USE_ECDSA_KEY)
	EVP_PKEY *key = EVP_EC_gen("P-256");
#else
	EVP_PKEY *key = EVP_RSA_gen(2048);
#endif
#else
	EVP_PKEY *key = EVP_PKEY_new();
#if defined(USE_ECDSA_KEY) && (OPENSSL_VERSION_NUMBER >= 0x10000000L)
	EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
	EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE);
	EC_KEY_generate_key(ec_key);
	EVP_PKEY_assign_EC_KEY(key, ec_key);
#else
	bn = BN_new();
	BN_set_word(bn, RSA_F4);
	RSA *rsa = RSA_new();
	RSA_generate_key_ex(rsa, 2048, bn, NULL);
	EVP_PKEY_assign_RSA(key, rsa);
	BN_free(bn);
#endif
#endif
	X509 *cert = X509_new();
	X509_set_version(cert, 2);
	ASN1_INTEGER *serial = ASN1_INTEGER_new();
	bn = BN_new();
	BN_rand(bn, 64, 0, 0);
	BN_to_ASN1_INTEGER(bn, serial);
	X509_set_serialNumber(cert, serial);
	ASN1_INTEGER_free(serial);
	BN_free(bn);
	X509_name_st *name = X509_get_subject_name(cert);
	X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, (unsigned char *)"JP", -1, -1, 0);
	X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, (unsigned char *)"proxy2ch certificate generator", -1, -1, 0);
	X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"proxy2ch", -1, -1, 0);
	X509_set_subject_name(cert, name);
	X509_set_issuer_name(cert, name);
	X509_set_pubkey(cert, key);
	X509_gmtime_adj(X509_get_notBefore(cert), 0);
	X509_gmtime_adj(X509_get_notAfter(cert), 31536000*3);
	add_ext(cert, NID_basic_constraints, "critical,CA:TRUE");
	add_ext(cert, NID_key_usage, "critical,digitalSignature,keyCertSign,cRLSign");
	add_ext(cert, NID_ext_key_usage, "serverAuth,clientAuth");
	add_ext(cert, NID_subject_key_identifier, "hash");
	X509_sign(cert, key, EVP_sha256());

	PEM_write_X509(stdout, cert);
	PEM_write_PrivateKey(stdout, key, NULL, NULL, 0, NULL, NULL);

	X509_free(cert);
	EVP_PKEY_free(key);
}

BBS2chProxySecureSocket::BBS2chProxySecureSocket(int sock, const char *host) :
socket(sock), ctx(NULL), ssl(NULL)
{
	bool hostIsDomain = false;
	for (int i=strlen(host)-1; i>=0; i--) {
		if (host[i] != '.' && !(host[i] >= '0' && host[i] <= '9')) {
			hostIsDomain = true;
			break;
		}
	}
	X509 *cert = X509_new();
	X509_set_version(cert, 2);
	ASN1_INTEGER *serial = ASN1_INTEGER_new();
	BIGNUM *bn = BN_new();
	BN_rand(bn, 64, 0, 0);
	BN_to_ASN1_INTEGER(bn, serial);
	X509_set_serialNumber(cert, serial);
	ASN1_INTEGER_free(serial);
	BN_free(bn);
	X509_name_st *name = X509_get_subject_name(cert);
	X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, (unsigned char *)"JP", -1, -1, 0);
	X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, (unsigned char *)"proxy2ch", -1, -1, 0);
	X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)host, -1, -1, 0);
	X509_set_subject_name(cert, name);
	X509_set_issuer_name(cert, X509_get_subject_name(ca_cert));
	X509_set_pubkey(cert, server_privkey);
	X509_gmtime_adj(X509_get_notBefore(cert), -600);
	X509_gmtime_adj(X509_get_notAfter(cert), 31536000);
	add_ext(cert, NID_basic_constraints, "critical,CA:FALSE");
	add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment");
	add_ext(cert, NID_ext_key_usage, "serverAuth,clientAuth");
	add_ext(cert, NID_subject_key_identifier, "hash");
	std::string sni(hostIsDomain ? "DNS:" : "IP:");
	sni += host;
	sni += ",DNS:*.5ch.net,DNS:*.2ch.net,DNS:*.bbspink.com";
	add_ext(cert, NID_subject_alt_name, sni.c_str());
	X509_sign(cert, ca_privkey, EVP_sha256());

	ctx = SSL_CTX_new(TLS_server_method());
	if (!ctx) {
		X509_free(cert);
		throw std::runtime_error("Unable to create SSL context");
	}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(OPENSSL_NO_SECURITY_DOWNGRADE)
	/* Downgrade security level because 3.0 depreacted use of SHA-1 which is used by TLS v1.1 and earlier clients */
	SSL_CTX_set_security_level(ctx, 0);
#endif
	if (SSL_CTX_use_certificate(ctx, cert) <= 0) {
		log_printf(0, "Unable to load server certificate\n");
	}
	if (SSL_CTX_use_PrivateKey(ctx, server_privkey) <= 0) {
		log_printf(0, "Unable to load server private key\n");
	}
	X509_free(cert);
	ssl = SSL_new(ctx);
	SSL_set_fd(ssl, socket);
	if (SSL_accept(ssl) <= 0) {
		char errbuf[256];
		ERR_error_string_n(ERR_get_error(), errbuf, 256);
		SSL_free(ssl);
		SSL_CTX_free(ctx);
		std::string str("Unable to establish SSL/TLS connection: ");
		str += errbuf;
		throw std::runtime_error(str);
	}
	log_printf(1, "MITM connection established with %s, %s\n", SSL_get_cipher_name(ssl), SSL_get_version(ssl));
}
#endif

BBS2chProxySecureSocket::~BBS2chProxySecureSocket()
{
}

int BBS2chProxySecureSocket::read(char *buffer, int length)
{
#ifdef USE_GNUTLS
	return gnutls_record_recv(session, buffer, length);
#elif defined(USE_MBEDTLS)
	return mbedtls_ssl_read(&ssl, (unsigned char *)buffer, length);
#else
	return SSL_read(ssl, buffer, length);
#endif
}

int BBS2chProxySecureSocket::readLine(char *buffer, int maxLength)
{
	char *ptr = buffer;
	while (ptr < buffer + maxLength - 1) {
#ifdef USE_GNUTLS
		int read = gnutls_record_recv(session, ptr, 1);
#elif defined(USE_MBEDTLS)
		int read = mbedtls_ssl_read(&ssl, (unsigned char *)ptr, 1);
#else
		int read = SSL_read(ssl, ptr, 1);
#endif
		if (read != 1) {
			return 0;
		}
		if (*ptr++ == '\n') {
			break;
		}
	}
	*ptr = 0;
	return 1;
}

int BBS2chProxySecureSocket::write(const char *buffer, int length)
{
#if defined(USE_GNUTLS) || defined(USE_MBEDTLS)
	int sent = 0;
	while (length > 0) {
#ifdef USE_GNUTLS
		int ret = gnutls_record_send(session, buffer+sent, length);
#else
		int ret = mbedtls_ssl_write(&ssl, (unsigned char *)buffer+sent, length);
#endif
		if (ret <= 0) break;
		sent += ret;
		length -= ret;
	}
	return sent;
#else
	return SSL_write(ssl, buffer, length);
#endif
}

int BBS2chProxySecureSocket::writeString(const std::string &str)
{
#if defined(USE_GNUTLS) || defined(USE_MBEDTLS)
	return write(str.data(), str.length());
#else
	return SSL_write(ssl, str.data(), str.length());
#endif
}

void BBS2chProxySecureSocket::close(void)
{
#ifdef USE_GNUTLS
	if (socket >= 0) {
		gnutls_bye(session, GNUTLS_SHUT_WR);
		SLEEP_MS(10);
		CLOSESOCKET(socket);
		socket = -1;
	}
	if (session) {
		gnutls_deinit(session);
		session = NULL;
	}
	if (x509_cred) {
		gnutls_certificate_free_credentials(x509_cred);
		x509_cred = NULL;
	}
#elif defined(USE_MBEDTLS)
	if (client.fd >= 0) {
		mbedtls_ssl_close_notify(&ssl);
		shutdown(client.fd, SHUT_RDWR);
		SLEEP_MS(10);
		CLOSESOCKET(client.fd);
		client.fd = -1;
		mbedtls_ssl_free(&ssl);
		mbedtls_ssl_config_free(&conf);
		mbedtls_x509_crt_free(&server_cert);
	}
#else
	if (ssl) {
		SSL_shutdown(ssl);
		SSL_free(ssl);
		ssl = NULL;
		SLEEP_MS(10);
	}
	if (socket >= 0) {
		CLOSESOCKET(socket);
		socket = -1;
	}
	if (ctx) {
		SSL_CTX_free(ctx);
		ctx = NULL;
	}
#endif
}
#endif
